
#pragma comment(lib,"glfwdll.lib")
#pragma comment(lib,"opengl32.lib")
#pragma comment( lib, "glew32.lib" )

//#define _CRT_SECURE_NO_DEPRECATE

#include <stdio.h>
#include <stdlib.h>
#define _USE_MATH_DEFINES
#include <math.h>

#include <string>	

#define GLFW_NO_GLU
#include <gl\glew.h>
#include <gl\glfw.h>
#include "OpenGLHelpers.h" // glu replacements
#include "OpenGLDebug.h"
#include "shapes3d.h"
#include "TesselCube.h"
#include "ShadowManager.h"

using namespace std;

#define SCREEN_WIDTH 800								// We want our screen width 800 pixels
#define SCREEN_HEIGHT 600								// We want our screen height 600 pixels

#include "Camera.h"
#include "CShader.h"
using namespace Shapes3D;
 
TesselCube tesselatedCubeoid;
ShadowManager shadowManager;
CCamera g_Camera;

bool Init()
{
	if( glfwInit() != GL_TRUE) return false;

	glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE, GL_TRUE);
	//glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 2);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 1);

	if( glfwOpenWindow(SCREEN_WIDTH,SCREEN_HEIGHT,0,0,0,0,0,0,GLFW_WINDOW) != GL_TRUE ) return false;

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		printf("GLEW ERROR: %s\n", glewGetErrorString(err));
		return false;
	}
	if( !GLEW_VERSION_2_1)
	{
		printf("You must have OpenGL 2.1 support to run this program!\n");
		return false;
	}
	
	if(GLEW_ARB_multitexture) 
	{
		printf( "yay! GL_ARB_multitexture extension supported! so it should also support glMultiTexCoordARB...\n" );
	}else{
		printf( "Your current setup does NOT support the GL_ARB_multitexture extension\n" );
		return false;
	}

	/* If you use GLEW, comment these out, as GLEW seems
		to have some weird issues with forward-compatible core profiles.
		Otherwise you should always use forward-compatible core profiles
		in order to get proper error-reporting from OpenGL if you use outdated
		functionality that makes performance suffer.
		FUNCTION-POINTERS YOURSELF */

	//glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	//glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)

	// This turns the background to a dark grey/black.
	glClearColor(0.2f, 0.2f, 0.2f, 1.0f);

	// Turn on our light and enable color along with the light
	glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);
	// set material properties which will be assigned by glColor
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	glMateriali(GL_FRONT, GL_SHININESS, 96);

	// Set the camera:		Position		View		 Up Vector
	g_Camera.PositionCamera(0, 9, 12,     0, 2.5, -2,     0, 1, 0);
	InitGLSL();		
	
	tesselatedCubeoid.init();
	shadowManager.init(SCREEN_WIDTH, SCREEN_HEIGHT);
	return true;
}


bool AnimateNextFrame(int desiredFrameRate)
{
	static float lastTime = (float)glfwGetTime();
	static float elapsedTime = 0.0f;

	float currentTime = (float)glfwGetTime();
	float deltaTime = currentTime - lastTime; // Get the slice of time
	float desiredFPS = 1.0f / desiredFrameRate; // Store 1 / desiredFrameRate

	elapsedTime += deltaTime; // Add to the elapsed time
	lastTime = currentTime; // Update lastTime

	// Check if the time since we last checked is greater than our desiredFPS
	if( elapsedTime > desiredFPS )
	{
		elapsedTime -= desiredFPS; // Adjust our elapsed time

		// Return true, to animate the next frame of animation
		return true;
	}

	// We don't animate right now.
	return false;
}

void SizeOpenGLScreen(int width, int height)
{
	if (height==0)										// Prevent A Divide By Zero error
	{
		height=1;										// Make the Height Equal One
	}
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);					
	glLoadIdentity();	
	glusPerspective(45.0f,(GLfloat)width/(GLfloat)height, 1.0f, 500.0f);
	glMatrixMode(GL_MODELVIEW);				
	glLoadIdentity();
}

void DrawWalls()
{
	// This is the size of our walls for each side of the axis
	float size = 5.0f;

	// Start rendering the walls as QUADS
	glBegin(GL_QUADS);

	// Back
	glNormal3f(0, 0, 1);
	glColor4f(1.0f, 0.0f, 1.0f, 1.0);	// Make this wall pink
	glVertex3f(-size, size, 0);			
	glVertex3f(-size, -size, 0);	
	glVertex3f(size, -size, 0);
	glVertex3f(size, size, 0);

	// Left
	glNormal3f(1, 0, 0);
	glColor4f(1.0f, 0.0f, 0.0f, 1.0);
	glVertex3f(-size, size, size);
	glVertex3f(-size, -size, size);
	glVertex3f(-size, -size, 0);
	glVertex3f(-size, size, 0);

	// Right
	glNormal3f(-1, 0, 0);
	glColor4f(0.0f, 0.0f, 1.0f, 1.0);
	glVertex3f(size, size, 0);
	glVertex3f(size, -size, 0);
	glVertex3f(size, -size, size);
	glVertex3f(size, size, size);

	// Bottom
	glNormal3f(0, 1, 0);
	glColor4f(0.0f, 1.0f, 0.0f, 1.0);
	glVertex3f(size, -size, size);
	glVertex3f(size, -size, 0);
	glVertex3f(-size, -size, 0);
	glVertex3f(-size, -size, size);

	glEnd();
}

void RenderWorld(bool depthRender, bool shadowTexturing)
{
	// Draw our walls and move them back into the screen by 15
	// Instead of calling glTranslatef, we need a custom function that also maintain the light matrix
	shadowManager.startTranslate(0, 0, -15);
	DrawWalls();
	shadowManager.endTranslate();


	int mx,my;
	glfwGetMousePos(&mx,&my);
	float rotatx = 360 * mx / 600.0f;
	float rotaty = 360 * my / 800.0f;
	//glRotatef( , 1, 0, 0);
	//glRotatef( , 0, 1, 0);
	
	shadowManager.startTranslate(0,0,-10);
	//shadowManager.startRotate(rotatx,1,0,0); // duh. push/pop wrong matrix! todo: fix 
	//shadowManager.startRotate(rotaty,0,0,1);
	if(depthRender) glCullFace(GL_BACK);
	else glCullFace(GL_FRONT);
	glColor4f(0.0f, 1.0f, 0.1f, 1.0);
	FastTeapot(4, 1);
	if(depthRender) glCullFace(GL_FRONT);
	else glCullFace(GL_BACK);
	//shadowManager.endRotate();
	//shadowManager.endRotate();
	shadowManager.endTranslate();

	static float rotY = 0;
	rotY++;
	//glRotatef(rotY++, 0, 1, 0);
	
	//shadowManager.startTranslate(-2, 0, -10);
	shadowManager.startRotate(rotY,0,1,0);
	DrawCylinder(.5, 3, 12, true);	
	shadowManager.endRotate();
	//shadowManager.endTranslate();

	glColor4f(1.0f, 1.0f, 0.1f, 1.0);
	shadowManager.startTranslate(3, 0, -8);
	DrawTorus(0.5, 1);	
	shadowManager.endTranslate();	
}


void RenderScene() 
{
	shadowManager.beginFirstPass();
	RenderWorld(true,false);
	shadowManager.endFirstPass();

	glLoadIdentity();										
	g_Camera.Look();
	float inverseCamMat[16];
	g_Camera.GetInverseMatrix(&inverseCamMat[0]);

	shadowManager.beginLastPass(inverseCamMat);
	RenderWorld(false,true);
	shadowManager.endLastPass();
	//RenderWorld(false,false);

	// Draw the light at the light's position
	glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
	glTranslatef(shadowManager.g_LightPosition[0], shadowManager.g_LightPosition[1], shadowManager.g_LightPosition[2]);
	DrawSphere(0.05f, 25, 25);

	glColor3f(1,1,1);
	shadowManager.debugRender();
}

int main()
{
	if( !Init() )
	{
		return 1;
	}

	while(1)
	{										
		glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		SizeOpenGLScreen(SCREEN_WIDTH, SCREEN_HEIGHT);					// Setup the screen translations and viewport

			if(AnimateNextFrame(60))					// Render the scene only at 60 FPS
			{
				g_Camera.Update();						// Handle our camera movement
				RenderScene();							// Render the scene every frame 
			}
			if( !glfwGetWindowParam(GLFW_OPENED) ) break;
			if( glfwGetKey(GLFW_KEY_ESC)==1 ) break;
			
			bool err = false;
			if( checkGLErrors() )
			{
				err = true;
			}
			
			glfwSwapBuffers();
	}

	glfwTerminate();
	return 0;									// Return from the program
}
